#pragma once
#include <iostream>
#include <pthread.h>
#include <queue>
#include <unistd.h>

using namespace std;

const int gcap = 5; // 默认队列容量大小

template <class T>
class BlockQueue
{
public:
    BlockQueue(const int cap = gcap)
    :_capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_customCond, nullptr);
        pthread_cond_init(&_productCond, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_customCond);
        pthread_cond_destroy(&_productCond);
    }
    bool isFULL() { return _q.size() == _capacity; }
    bool isEMPTY() { return _q.empty(); }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while(isFULL()) // 只能在临界区内部判断临界资源是否就绪
        {
            // 在临界区内部则一定有锁
            // 为了让线程休眠等待，不产生死锁，则一定不能持有锁等待
            pthread_cond_wait(&_productCond, &_mutex); // 队列满则阻塞等待，wait操作会自动释放锁
            // 当线程醒来时，一定会在临界区内部继续运行。所以还需要重新申请锁，申请成功后才会执行wait后的代码
        }
        _q.push(in);

        pthread_cond_signal(&_customCond); // 只要生产者生产了，就告知消费者可以消费了
        pthread_mutex_unlock(&_mutex);
    }

    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while(isEMPTY())
        {
            pthread_cond_wait(&_customCond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_productCond);
        pthread_mutex_unlock(&_mutex);
    }

private:
    int _capacity;
    std::queue<T> _q;
    pthread_mutex_t _mutex;
    //为什么只要一把锁？根本原因在于生产和消费访问的是同一个queue
    //queue被当作整体使用

    // 阻塞队列为空就不能让消费者消费
    // 阻塞队列已满就不能让生产者生产
    // 因此不同的角色有不同的条件
    // 所以不能只是一个条件变量，需要两个分别表示消费者和生产者
    pthread_cond_t _customCond;  // 消费者
    pthread_cond_t _productCond; // 生产者
};
